/**
 * WebIM UI Component
 */

layui.define(['layer', 'laytpl', 'form', 'util', 'upload', 'dropdown', 'code'], function(exports) {
  "use strict";

  var v = '4.0.0';
  var $ = layui.$;
  var layer = layui.layer;
  var laytpl = layui.laytpl;
  var form = layui.form;
  var util = layui.util;
  var device = layui.device();

  // 模块名
  var MOD_NAME = 'layim';

  var SHOW = 'layui-show';
  var THIS = 'layim-this';
  var PENDING = 'pending';

  // constructor
  var LAYIM = function(){
    this.v = v;
  };

  // 缓存数据
  var cache = { callback: {}, chat: [], message: {} };

  // 基础配置
  LAYIM.prototype.config = function(options) {
    var contactsPanel = {
      showFriend: true, // 联系人面板中是否显示好友列表
      showGroup: true // 联系人面板中是否显示群聊列表
    };
    var defaultAvatar = options.defaultAvatar || resPath('images/default.png');

    // 默认配置项
    options = $.extend(true, {
      contactsPanel: contactsPanel, // 联系人面板
      backgroundImage: false, // 初始背景图，images/background 目录 - boolean | string
      voice: 'default.mp3', // 用于新消息提示的音频文件，voice 目录
      LOCAL_TABLE: 'layim', // 本地存储表名
      LOCAL_MAX_CHATLOG: 20, // 存储在本地的最多聊天记录数
      chatTimeout: 10000, // 接受会话消息时，等待响应的最大时长 (ms)
      defaultAvatar: defaultAvatar // 默认头像
    }, options);

    // 自适配选项值
    if (options.contactsPanel === true) {
      options.contactsPanel = contactsPanel;
    }

    // 同步到 cache 对象中
    $.extend(true, cache, {
      options: options
    })

    if(!window.JSON || !window.JSON.parse) return;
    init(cache.options);
    return this;
  };

  // 事件
  LAYIM.prototype.on = function(events, callback){
    return layui.onevent.call(this, MOD_NAME, events, callback);
    return this;
  };

  // 获取所有缓存数据
  LAYIM.prototype.cache = function(){
    return cache;
  };

  // 设置当前会话状态说明
  LAYIM.prototype.setChatStatus = function(str){
    var thisChat = currentChat();
    if(!thisChat) return;
    var status = thisChat.elem.find('.layim-chat-status');
    return status.html(str), this;
  };

  // 桌面消息通知
  LAYIM.prototype.notice = function(data){
    return notice(data), this;
  };

  // 设置好友在线/离线状态
  LAYIM.prototype.setFriendStatus = function(id, status){
    var list = $('.layim-friend'+ id);
    list[status === 'online' ? 'removeClass' : 'addClass']('layim-list-gray');
  };

  /**
   * 扩展聊天面板工具栏
   * @param {Object} tools - 工具配置项
   */
  var extendChatTools = LAYIM.prototype.extendChatTools = function(tools) {
    var options = cache.options;
    var chatTools = options.chatTools || [];

    tools = layui.type(tools) !== 'array' ? [tools] : tools;

    // 替换已存在的同名工具
    layui.each(tools, function(n1, o1) {
      layui.each(chatTools, function(n2, o2) {
        if (o1.name && o1.name === o2.name) {
          $.extend(true, o2, o1);
          tools.splice(n1, 1);
        }
      });
    });

    options.chatTools = chatTools.concat(tools);
  };

  /**
   * 设置或获取回调函数
   * @param {string} name
   * @param {Function} handle
   */
  var callback = LAYIM.prototype.callback = function(name, handle) {
    var callback = cache.callback;
    if (typeof handle === 'function') {
      callback[name] = handle;
      cache.callback = callback;
    } else {
      return callback[name];
    }
  };

  // 静态资源路径
  var resPath = function(file) {
    return layui.cache.layimResPath + file;
  };

  // 联系人面板中的列表模板
  var listTpl = function(options) {
    var nodata = {
      friend: "该分组下暂无好友",
      group: "暂无群组",
      history: "暂无历史会话"
    };

    options = options || {};
    options.item = options.item || ('d.' + options.type);

    return ['{{# var length = 0; layui.each('+ options.item +', function(i, data){ length++; }}'
      ,'<li layim-event="chat" data-type="'+ options.type +'" data-index="{{= '+ (options.index||'i') +' }}" class="layim-'+ (options.type === 'history' ? '{{=i}}' : options.type + '{{=data.id}}') +' {{= data.status === "offline" ? "layim-list-gray" : "" }}"><img src="{{= data.avatar || layui.layim.cache().options.defaultAvatar }}"><span>{{= data.username||data.groupname||data.username||"佚名" }}</span><p>{{= data.remark||data.sign||"" }}</p><span class="layim-msg-status">new</span></li>'
    ,'{{# }); if(length === 0){ }}'
      ,'<li class="layim-null">'+ (nodata[options.type] || "暂无数据") +'</li>'
    ,'{{# } }}'].join('');
  };

  // 联系人主模板
  var elemTpl = ['<div class="layui-layim-main">',
    '{{# var local = layui.layim.getLocalData(); ',
      'var contactsPanel = d.options.contactsPanel;',
    '}}',
    '<div class="layui-layim-info">',
      '<div class="layui-layim-user">{{= d.user.username }}</div>',
      '<div class="layui-layim-status">',
        '{{# if(d.user.status === "online"){ }}',
        '<span class="layui-icon layim-status-online" layim-event="status" lay-type="show">&#xe617;</span>',
        '{{# } else if(d.user.status === "hide") { }}',
        '<span class="layui-icon layim-status-hide" layim-event="status" lay-type="show">&#xe60f;</span>',
        '{{# } }}',
        '<ul class="layui-anim layim-border layim-menu-box">',
          '<li {{=d.user.status === "online" ? "class=layim-this" : ""}} layim-event="status" lay-type="online"><i class="layui-icon">&#xe605;</i><cite class="layui-icon layim-status-online">&#xe617;</cite>在线</li>',
          '<li {{=d.user.status === "hide" ? "class=layim-this" : ""}} layim-event="status" lay-type="hide"><i class="layui-icon">&#xe605;</i><cite class="layui-icon layim-status-hide">&#xe60f;</cite>隐身</li>',
        '</ul>',
      '</div>',
      '<input class="layui-layim-remark" placeholder="编辑签名" value="{{- d.user.remark||d.user.sign||"" }}">',
    '</div>',
    '<ul class="layui-unselect layim-border layui-layim-tab{{# if(!contactsPanel.showFriend || !contactsPanel.showGroup){ }}',
      ' layim-tab-two',
    '{{# } }}">',
      '<li class="layui-icon',
      '{{# if(!contactsPanel.showFriend){ }}',
      ' layim-hide',
      '{{# } else { }}',
      ' layim-this',
      '{{# } }}',
      '" title="联系人" layim-event="tab" lay-type="friend">&#xe612;</li>',
      '<li class="layui-icon',
      '{{# if(!contactsPanel.showGroup){ }}',
      ' layim-hide',
      '{{# } else if(!contactsPanel.showFriend) { }}',
      ' layim-this',
      '{{# } }}',
      '" title="群组" layim-event="tab" lay-type="group">&#xe613;</li>',
      '<li class="layui-icon" title="历史会话" layim-event="tab" lay-type="history">&#xe611;</li>',
    '</ul>',
    '<ul class="layui-unselect layim-tab-content layim-scrollbar {{# if(contactsPanel.showFriend){ }}layui-show{{# } }} layim-list-friend">',
    '{{# layui.each(d.friend, function(index, item){ var spread = local["spread"+index]; }}',
      '<li>',
        '<h5 layim-event="spread" lay-type="{{= spread }}"><i class="layui-icon">{{# if(spread === "true"){ }}&#xe61a;{{# } else {  }}&#xe602;{{# } }}</i><span>{{= item.groupname||"未命名分组"+index }}</span><em>(<cite class="layim-count"> {{= (item.list||[]).length }}</cite>)</em></h5>',
        '<ul class="layui-layim-list {{# if(spread === "true"){ }}',
        ' layui-show',
        '{{# } }}">',
        listTpl({
          type: "friend",
          item: "item.list",
          index: "index"
        }),
        '</ul>',
      '</li>',
    '{{# }); if(d.friend.length === 0){ }}',
      '<li><ul class="layui-layim-list layui-show"><li class="layim-null">暂无联系人</li></ul>',
    '{{# } }}',
    '</ul>',
    '<ul class="layui-unselect layim-tab-content layim-scrollbar {{# if(!contactsPanel.showFriend && contactsPanel.showGroup){ }}layui-show{{# } }}">',
      '<li>',
        '<ul class="layui-layim-list layui-show layim-list-group">',
        listTpl({
          type: 'group'
        }),
        '</ul>',
      '</li>',
    '</ul>',
    '<ul class="layui-unselect layim-tab-content layim-scrollbar {{# if(!contactsPanel.showFriend && !contactsPanel.showGroup){ }}layui-show{{# } }}">',
      '<li>',
        '<ul class="layui-layim-list layui-show layim-list-history">',
        listTpl({
          type: 'history'
        }),
        '</ul>',
      '</li>',
    '</ul>',
    '<ul class="layui-unselect layim-tab-content">',
      '<li>',
        '<ul class="layui-layim-list layui-show" id="layui-layim-search"></ul>',
      '</li>',
    '</ul>',
    '<ul class="layui-unselect layui-layim-tool layim-border">',
      '<li class="layui-icon layui-icon-search layim-tool-search" layim-event="search" title="搜索"></li>',
      '{{# if(d.options.pageurl && d.options.pageurl.msgbox){ }}',
      '<li class="layui-icon layui-icon-speaker layim-tool-msgbox" layim-event="msgbox" title="消息盒子"><span class="layui-anim"></span></li>',
      '{{# } }}',
      '{{# if(d.options.pageurl && d.options.pageurl.find){ }}',
      '<li class="layui-icon layim-tool-find" layim-event="find" title="查找">&#xe608;</li>',
      '{{# } }}',
      '<li class="layui-icon layui-icon-set layui-font-18" layim-event="set" title="设置"></li>',
    '</ul>',
    '<div class="layui-layim-search"><input><label class="layui-icon" layim-event="closeSearch">&#x1007;</label></div>',
  '</div>'].join('');

  // 设置模板
  var elemSetTpl = [
    '<div class="layui-form" lay-filter="layim-set">',
      '<div class="layui-form-item">',
        '<label class="layui-form-label">主题模式</label>',
        '<div class="layui-form-block">',
          '<input type="checkbox" name="theme" lay-filter="layim-theme-mode" lay-skin="switch">',
          '<div lay-checkbox>',
            '<i class="layui-icon layui-icon-moon"></i> |',
            '<i class="layui-icon layui-icon-light"></i>',
          '</div>',
        '</div>',
      '</div>',
      '<div class="layui-form-item">',
        '<label class="layui-form-label">背景底图</label>',
        '<div class="layui-form-block layui-clear">',
          '<ul class="layui-layim-skin">',
            '{{# layui.each(d.backgroundImages, function(index, item){ }}',
            '<li><img layim-event="setBackgroundImage" src="{{= item }}"></li>',
            '{{# }); }}',
            '<li layim-event="setBackgroundImage"><cite>无图</cite></li>',
          '</ul>',
        '</div>',
      '</div>',
    '</div>'
  ].join('')

  // 聊天面板
  var elemChatTpl = ['<div class="layim-chat layim-chat-{{=d.data.type}}{{=d.first ? " layui-show" : ""}}">',
    '<div class="layui-unselect layim-chat-title">',
      '<div class="layim-chat-other">',
        '<img class="layim-{{= d.data.type }}{{= d.data.id }}" src="{{= d.data.avatar || layui.layim.cache().options.defaultAvatar }}"><span class="layim-chat-username" layim-event="{{= d.data.type==="group" ? \"groupMembers\" : \"\" }}">{{= d.data.username||"佚名" }} {{- d.data.temporary ? "<cite>临时会话</cite>" : "" }} {{# if(d.data.type==="group"){ }} <em class="layim-chat-members"></em><i class="layui-icon">&#xe61a;</i> {{# } }}</span>',
        '<span class="layim-chat-status"></span>',
      '</div>',
    '</div>',
    '<div class="layim-chat-main layim-scrollbar"><ul></ul></div>',
    '<div class="layim-chat-footer layim-border">',
      '<div class="layui-unselect layim-chat-tool">',
      '{{# layui.each(d.options.chatTools, function(i, item){ }}',
        '{{# if(item.hide) return; }}',
        '{{# var onClickType = typeof item.onClick; }}',
        '{{# var eventName = onClickType === "function" ? "extendChatTool" : (onClickType === "string" ? item.onClick : item.name); }}',
        '<span class="layui-icon {{=item.icon||\'\'}} layim-tool-{{=item.name}}" title="{{=item.title||\'\'}}" layim-event="{{=eventName}}" data-chat-toolname="{{=item.name}}">',
        '{{# if (item.onClick === "file") { }}',
          '<input type="file" name="file">',
        '{{# } }}',
        '</span>',
      '{{# }); }}',
      '</div>',
      '<div class="layim-chat-textarea"><textarea class="layim-scrollbar"></textarea></div>',
      '<div class="layim-chat-bottom">',
        '<div class="layim-chat-send">',
          '<span class="layim-send-close" layim-event="closeThisChat">关闭</span>',
          '<span class="layim-send-btn" layim-event="send">发送</span>',
          '<span class="layim-send-set layim-border" layim-event="setSend" lay-type="show"><i class="layui-icon layui-icon-down"></i></span>',
          '<ul class="layui-anim layim-border layim-menu-box">',
            '<li {{=d.local.sendHotKey !== "Ctrl+Enter" ? "class=layim-this" : ""}} layim-event="setSend" lay-type="Enter"><i class="layui-icon">&#xe605;</i>按 Enter 键发送消息</li>',
            '<li {{=d.local.sendHotKey === "Ctrl+Enter" ? "class=layim-this" : ""}} layim-event="setSend"  lay-type="Ctrl+Enter"><i class="layui-icon">&#xe605;</i>按 Ctrl+Enter 键发送消息</li>',
          '</ul>',
        '</div>',
      '</div>',
    '</div>',
  '</div>'].join('');

  // 添加好友群组模版
  var elemAddTpl = ['<div class="layim-add-box">',
    '{{# var cache = layui.layim.cache(); var user = cache.user || {}; }}',
    '<div class="layim-add-img"><img class="layui-circle" src="{{= d.data.avatar || cache.options.defaultAvatar }}"><p>{{= d.data.username||"" }}</p></div>',
    '<div class="layim-add-remark">',
    '{{# if(d.type === "addFriend"){ }}',
      '<p>选择分组：</p>',
    '{{# } }}',
    '{{# if(d.data.type === "friend"){ }}',
      '<select class="layui-select" id="LAY_layimGroup">',
        '{{# layui.each(d.data.group, function(index, item){ }}',
        '<option value="{{= item.id }}">{{= item.groupname }}</option>',
        '{{# }); }}',
      '</select>',
    '{{# } }}',
    '{{# if(d.data.type === "group"){ }}',
      '<p>填写验证信息：</p>',
    '{{# } }}',
    '{{# if(d.type !== "addFriend"){ }}',
      '<textarea id="LAY_layimRemark" placeholder="验证信息" class="layui-textarea">我是{{= user.username || "" }}</textarea>',
    '{{# } }}',
    '</div>',
  '</div>'].join('');

  // 聊天内容列表模版
  var elemChatMain = [
    '<li {{= d.user ? "class=layim-chat-role-user" : "" }} {{# if(d.mid){ }}data-mid="{{=d.mid}}"{{# } }} {{= d.status || "" }}>',
      '<div class="layim-chat-userinfo">',
        '<img src="{{= d.avatar || layui.layim.cache().options.defaultAvatar }}"><cite>',
        '{{# if(d.user){ }}',
          '<i>{{= layui.util.toDateString(d.timestamp) }}</i>{{= d.username||"佚名" }}',
         '{{# } else { }}',
          '{{= d.username||"佚名" }}<i>{{= layui.util.toDateString(d.timestamp) }}</i>',
         '{{# } }}',
        '</cite>',
      '</div>',
      '{{# var content = d.content || "&nbsp"; }}',
      '<div class="layim-chat-text layui-text">',
        // 注意：若在外部自定义内容解析器，请务必在过滤 XSS 字符，确保返回安全的 HTML 内容
        '{{- content }}',
      '</div>',
    '</li>'
  ].join('');

  // 已打开的会话列表
  var elemChatList = '<li class="layim-{{= d.data.type }}{{= d.data.id }} layim-chatlist-{{= d.data.type }}{{= d.data.id }} layim-this" layim-event="tabChat"><img src="{{= d.data.avatar || layui.layim.cache().options.defaultAvatar }}"><span>{{= d.data.username||"佚名" }}</span><i class="layui-icon layui-icon-close-fill" layim-event="closeChat"></i></li>';

  // 默认内容解析器
  var defaultContentParser = cache.callback.contentParser = function(content) {
    content = (content||'').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
    .replace(/</g, '&lt;').replace(/'/g, '&#39;').replace(/"/g, '&quot;') // 转义 XSS
    .replace(/.+\n?/g, function(str) { // 匹配整行
      var trimStr = str.trim().replace(/\s/g, '');
      // 转义水平线
      if (/^\-(\-+)\-$/.test(trimStr)) {
        return '<hr>';
      }
      // 转义引用
      if (/^\>.*$/.test(trimStr)) {
        return str.replace(/^.*\>(.*)\n?$/, '<blockquote>$1</blockquote>');
      }
      return str;
    })
    .replace(/!\[(.*?)\]\((.*?)\)/g, function(str, alt, src) {  // 转义图片
      return '<img src="'+ src +'" alt="'+ alt +'">';
    })

    /**
     * 更多文件类型扩展
     */
    .replace(/!file\[(.*?)\]\((.*?)\)/g, function(str, title, href) { // 转义文件
      return [
        '<a class="layui-layim-file" href="'+ href +'" download target="_blank">',
          '<i class="layui-icon layui-icon-layim-download"></i>',
          '<cite>'+ title +'</cite>',
        '</a>'
      ].join('');
    })
    .replace(/!audio\((.*?)\)/g, function(str, src) {  // 转义语音
      return [
        '<div class="layui-unselect layui-layim-audio" layim-event="playAudio" data-src="' + src + '">',
          '<i class="layui-icon layui-icon-play"></i>',
          '<p>语音消息</p>',
        '</div>'
      ].join('');
    })
    .replace(/!video\((.*?)\)/g, function(str, src) {  // 转义视频
      return [
        '<div class="layui-unselect layui-layim-video" layim-event="playVideo" data-src="' + src + '">',
          '<i class="layui-icon layui-icon-play"></i>',
        '</div>'
      ].join('');
    })

    .replace(/\[(.*?)\]\((.+?)\)/g, function(str, text, href) { // 转义链接
      text = text.replace(/^\s+|\s+$/g, '');
      return '<a href="'+ href +'" target="_blank">'+ (text || href) +'</a>';
    })
    .replace(/\*\*(?!\s)([^\*\r\n]+?)(?<!\s)\*\*/g, function(str, text) { // 转义粗体
      return '<strong>'+ text +'</strong>';
    })
    .replace(/\*(?!\s)([^\*\r\n]+?)(?<!\s)\*/g, function(str, text) { // 转义斜体
      return '<em>'+ text +'</em>';
    })
    .replace(/(?<!#)(#{1,6}) +(.+)\n?/g, function(str, tag, text) { // 转义标题
      var TAG = 'h'+ tag.length;
      return '<'+ TAG + '>'+ text +'</'+ TAG + '>';
    })

    .replace(/\`{3}(\w*)\n([\s\S]*?)\`{3}/g, function(str, lang, code) { // 转义 code 块
      lang = lang ? 'language-'+ lang : '';
      return '<pre><code class="'+ lang  +'">'+ code.replace(/\n/g, '\\KN') +'</code></pre>'
    })
    .replace(/\n/g, '<br>') // 转义换行
    .replace(/\\KN/g, '\n') // 保留换行符
    return content;
  };

  /**
   * 解析聊天内容
   * @param {string} content
   */
  var contentParser = function(content) {
    var contentParser = callback('contentParser');
    return contentParser ? contentParser(content) : defaultContentParser(content);;
  };

  // jQuery ajax
  var ajax = function(options, callback) {
    options = $.extend({
      type: 'get',
      dataType: 'json',
      error: console.error
    }, options);

    // 成功的回调
    var success = options.success;
    options.success = function(res) {
      var data = res.data || {};
      typeof callback === 'function' && callback(data);
      typeof success === 'function' && success.apply(null, arguments);
    };

    return $.ajax(options);
  };

  // 向目标元素生成 loading 元素
  var loading = function(target) {
    var CLASS_LOADING = 'layui-layim-loading';
    var HTML_ICON = '<i class="layui-icon layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop"></i>';
    target.html('<span class="'+ CLASS_LOADING +'">'+ HTML_ICON + '</span>');
    return target.children('.'+ CLASS_LOADING);
  };

  // 处理初始化信息
  var init = function(options) {
    var init = options.init || {};
    var user = init.user || {};
    var contactsPanel = options.contactsPanel;

    // 数据集
    var extendCache = (function fn(user, more) {
      $.extend(true, cache, {
        options: options, // 基础配置信息
        user: user // 我的用户信息
      }, more);
      return fn;
    })(user);

    // 创建主面板
    var create = function(data) {
      var user = data.user || {};
      var local = getLocalData(user.id);

      // 追加主面板数据
      extendCache(user, {
        friend: data.friend || [], // 好友列表
        group: data.group || [], // 群组列表
        history: local.history || {} // 历史会话信息
      });

      popim(laytpl(elemTpl).render(cache)); // 主面板
      if (local.close || contactsPanel.minStatus) popmin(); // 主面板最小化状态

      ready(data);
    };
    // 初始就绪
    var ready = function(data) {
      var user = data.user || {};
      var local = getLocalData(user.id);

      // 初始设置主题风格
      var themeMode = options.theme || local.theme
      theme(themeMode);
      local.theme = themeMode;

      layui.data(options.LOCAL_TABLE, {
        key: cache.user.id,
        value: local
      });

      // ready 事件
      layui.event.call(data, MOD_NAME, 'ready', cache);
    };

    // 初始化 chatTools
    (function() {
      // 默认聊天工具菜单
      var defaultChatTools = [
        {
          name: 'face',
          title: '插入表情',
          icon: 'layui-icon-face-smile-fine'
        }
      ];
      // 如果配置了 charlog url
      var pageurl = options.pageurl || {};
      if (pageurl.chatlog) {
        defaultChatTools.push({
          name: 'chatlog',
          title: '聊天记录',
          icon: 'layui-icon-log layim-tool-log'
        });
      }
      options.chatTools = options.chatTools || defaultChatTools;
    })();

    // 不开启联系人面板，自由通过相关方法打开会话面板
    if (!options.contactsPanel) {
      return ready(init);
    }

    // 初始化主面板
    init.url ? ajax(init, create) : create(init);
  };

  // 显示主面板
  var layimMain;
  var popim = function(content) {
    var options = cache.options;
    var contactsPanel = options.contactsPanel;

    return layer.open({
      type: 1,
      area: ['260px', '518px'],
      skin: 'layui-layim layui-layim-layer',
      title: '&#8203;',
      offset: 'rb',
      id: 'layui-layim',
      shade: false,
      anim: 2,
      resize: false,
      content: content,
      success: function(layero) {
        layimMain = layero;

        setBackgroundImage(layero);

        if (layimClose) {
          layer.close(layimClose.attr('times'));
        }

        // 按最新会话重新排列
        var arr = [], historyElem = layero.find('.layim-list-history');
        historyElem.find('li').each(function(){
          arr.push($(this).prop('outerHTML'))
        });
        if(arr.length > 0){
          arr.reverse();
          historyElem.html(arr.join(''));
        }

        banRightMenu();
        events.sign();

        typeof contactsPanel.done === 'function' && contactsPanel.done.apply(null, arguments);
      },
      cancel: function(index){
        popmin();
        var local = getLocalData();
        local.close = true;
        layui.data(options.LOCAL_TABLE, {
          key: cache.user.id,
          value: local
        });
        return false;
      }
    });
  };

  /**
   * 读取本地存储
   * @param {any} uid
   */
  var getLocalData = LAYIM.prototype.getLocalData = function(uid) {
    var user = cache.user || {};
    uid = (uid === undefined || uid === null) ? user.id : uid;
    return layui.data(cache.options.LOCAL_TABLE)[uid] || {};
  };

  /**
   * 设置主题风格
   * @param {('light' | 'dark')} style - 主题风格
   */
  var theme = LAYIM.prototype.theme = function(style) {
    var ID = 'LAYIM-THEME';
    var CLASS_LAYIM = '.layui-layim';

    // 浅色 - 即恢复默认样式
    if (style === 'light') {
      return $('#LAY-STYLE-'+ ID).remove();
    }

    // 当前仅支持设置深色
    if (style !== 'dark') return;

    lay.style({
      id: ID,
      target: $(CLASS_LAYIM)[0] ? CLASS_LAYIM : 'body',
      text: laytpl([
        '.layim-scrollbar{scrollbar-color: rgba(255,255,255,.16) rgba(0,0,0,0);}',
        'body .layui-layim-layer{background-color: {{=d.bgcolor}}; color: {{=d.textColor1}}; }',
        'body .layui-layim-layer a{color: {{=d.textColor2}};}',
        'body .layui-layim-layer .layui-layer-title, .layim-tab-content, .layui-layim-tool, body .layui-layim-chat .layui-layer-title, .layim-chat{background: {{=d.bgcolor1}}; color: {{=d.textColor1}}; }',
        '.layim-chat-list{background: {{=d.bgcolor2}}; }',
        '.layui-layim-layer .layui-layer-setwin span, .layui-layim-info .layui-layim-user, .layui-layim-remark, .layui-layim-tab li, .layim-chat-other span{color:{{=d.textColor1}} ;}',
        '.layui-layim-layer .layui-layer-setwin .layui-layer-min:before, .layui-layim-layer .layui-layer-setwin .layui-layer-max:before, .layui-layim-layer .layui-layer-setwin .layui-layer-max:after{border-color: rgba(255,255,255,.3);}',
        '.layim-border{border-color: {{=d.borderColor1}};}',
        '.layim-menu-box{background-color: {{=d.bgcolor2_1}}; border-color: {{=d.borderColor1}};}',
        '.layim-menu-box li:hover{background-color: {{=d.bgcolor3_1}}; }',
        '.layui-layim-remark:hover, body .layui-layim-layer .layui-layer-title{border-color: {{=d.borderColor1}};}',
        '.layui-layim-remark:focus{border-color: {{=d.borderColor3}}; background: none;}',
        '.layui-layim-list li:hover{background-color: {{=d.bgcolor2}}; }',
        '.layui-layim-tool li:active{background-color: {{d.bgcolor3}}; }',
        '.layim-chat-text, .layim-chat-system span, .layim-chat-send span, .layui-layim-members ul, .layui-layim-face ul, .layui-layim-skin li{background-color: {{d.bgcolor2}}; color:{{=d.textColor1}}; }',
        '.layim-chat-text{color:{{=d.textColor2}} ;}',
        '.layim-chat-text:after{border-top-color: {{d.bgcolor2}};}',
        '.layim-chat-role-user .layim-chat-text{color: #000 ;}',
        '.layim-chat-tool span:hover{color: {{=d.textColor2}}; }',
        '.layim-chat-tool span:active{color: {{=d.textColor3}}; }',
        '.layim-chat-textarea textarea{color:{{=d.textColor2}};}',
        '.layim-loading > .layui-icon{color: {{=d.textColor1}}; }',
        '.layim-chat-text code:not(.layui-code-view, .layui-code-wrap), .layim-chat-send span:hover{background-color: {{d.bgcolor3}}; }',
        '.layim-chat-list li.layim-this, .layim-chat-list li:hover{background-color: rgba(255,255,255,.1); }',
        '.layim-chat-list li .layui-icon{color: {{=d.textColor1}}; }',
        '.layui-layim-layer .layui-form .layui-form-onswitch{background: none; border-color: {{=d.borderColor3}}; }',
        'body .layui-layim-min{border: none;}'
      ].join('')).render({
        bgcolor: 'rgba(50,50,50,1);',
        bgcolor1: 'rgba(50,50,50,.9);',
        bgcolor2: 'rgba(65,65,65,.9);',
        bgcolor2_1: 'rgba(65,65,65,1);',
        bgcolor3: 'rgba(77,77,77,.9);',
        bgcolor3_1: 'rgba(77,77,77,1);',
        textColor1: 'rgba(255,255,255,.7)',
        textColor2: 'rgba(255,255,255,.9)',
        textColor3: 'rgba(255,255,255,1)',
        borderColor1: 'rgba(255,255,255,.1)',
        borderColor3: 'rgba(255,255,255,.3)'
      })
    });
  };

  // 屏蔽主面板右键菜单
  var banRightMenu = function() {
    layimMain.on('contextmenu', function(event){
      event.cancelBubble = true
      event.returnValue = false;
      return false;
    });

    var hide = function(){
      layer.closeAll('tips');
    };

    // 自定义历史会话右键菜单
    layimMain.find('.layim-list-history').on('contextmenu', 'li', function(e) {
      var othis = $(this);
      var html = '<ul data-id="'+ othis[0].id +'" data-index="'+ othis.data('index') +'"><li layim-event="menuHistory" data-type="one">移除该会话</li><li layim-event="menuHistory" data-type="all">清空全部会话列表</li></ul>';

      if(othis.hasClass('layim-null')) return;

      layer.tips(html, this, {
        tips: 1,
        time: 0,
        anim: 5,
        fixed: true,
        skin: 'layui-layim-contextmenu',
        success: function(layero){
          layero.off('mousedown', layui.stope).on('mousedown', layui.stope);
        }
      });
      $(document).off('mousedown', hide).on('mousedown', hide);
      $(window).off('resize', hide).on('resize', hide);
    });
  }

  // 主面板最小化状态
  var layimClose;
  var popmin = function(content) {
    var options = cache.options;
    var user = cache.user || {};
    var contactsPanel = options.contactsPanel;

    if (layimClose) {
      layer.close(layimClose.attr('times'));
    }
    if(layimMain){
      layimMain.hide();
    }

    return layer.open({
      type: 1,
      title: false,
      id: 'layui-layim-close',
      skin: 'layui-layim-min layui-layim-close layui-layim-layer',
      shade: false,
      closeBtn: false,
      anim: 2,
      offset: 'rb',
      resize: false,
      content: [
        '<img src="'+ (user.avatar || options.defaultAvatar) +'">',
        function(text) {
          return text ? '<span>'+ text +'</span>' : '';
        }(content || user.username)
      ].join(''),
      move: '#layui-layim-close img',
      success: function(layero, index) {
        layimClose = layero;

        layero.on('click', function(){
          layer.close(index);
          layimMain.show();
          var local = getLocalData();
          delete local.close;
          layui.data(options.LOCAL_TABLE, {
            key: cache.user.id,
            value: local
          });
        });

        typeof contactsPanel.done === 'function' && contactsPanel.done.apply(null, arguments);
      }
    });
  };

  // 打开聊天面板
  var layimChat;
  var layimMin;
  var layimChatLayer;
  var chatIndex;
  var popchat = LAYIM.prototype.chat = function(data) {
    data = data || {};

    var ELEM_CHAT_BOX = 'layim-chat-box';
    var chatPanelElem = $('#layui-layim-chat');
    var renderData = {
      data: data,
      options: cache.options,
      local: getLocalData()
    };
    var setThisChatData = function(thisChatElem) {
      thisChatElem.data('receiver', data);
    };

    if (!data.id) {
      return console.error('未传入必要参数：id');
    }

    // 始终创建新的聊天面板
    if (data.new) {
      chatPanelElem.closest('.layui-layim-chat').remove();
      delete chatPanelElem[0]
    }

    // 聊天面板是否已生成
    if (chatPanelElem[0]) {
      var list = layimChat.find('.layim-chat-list');
      var listThat = list.find('.layim-chatlist-'+ data.type + data.id);
      var hasFull = layimChat.find('.layui-layer-max').hasClass('layui-layer-maxmin');
      var chatBox = chatPanelElem.children('.'+ ELEM_CHAT_BOX);

      // 如果是最小化，则还原窗口
      if(layimChat.css('display') === 'none'){
        layimChat.show();
      }

      if(layimMin){
        layer.close(layimMin.attr('times'));
      }

      // 如果出现多个聊天面板
      if(list.find('li').length === 1 && !listThat[0]){
        hasFull || layimChat.addClass('layui-layim-chat-more').css('width', 800);
        list.css({
          height: layimChat.height()
        }).show();
        chatBox.css('margin-left', '200px');
        layimChatLayer && layimChatLayer.offset();
      }

      // 打开的是非当前聊天面板，则新增面板
      if (!listThat[0]) {
        list.append(laytpl(elemChatList).render(renderData));
        chatBox.append(laytpl(elemChatTpl).render(renderData));

        setThisChatData(chatBox.children('.layim-chat:last'))
        syncGray(data);
        resizeChat();
      }

      changeChat(list.find('.layim-chatlist-'+ data.type + data.id));
      listThat[0] || renderChatlog();
      setHistory(data);
      hotkeySend();

      return chatIndex;
    }

    renderData.first = !0;

    var index = chatIndex = layer.open($.extend({
      type: 1,
      area: '600px',
      skin: 'layui-layim-chat layui-layim-layer',
      id: 'layui-layim-chat',
      title: '&#8203;',
      shade: false,
      maxmin: true,
      offset: 'auto',
      anim: 5,
      content: laytpl('<ul class="layui-unselect layim-scrollbar layim-chat-list">'+ elemChatList +'</ul><div class="'+ ELEM_CHAT_BOX +'">' + elemChatTpl + '</div>').render(renderData),
      success: function(layero, index, layerInst) {
        layimChat = layero;
        layimChatLayer = layerInst;

        layero.css({
          'min-width': '500px',
          'min-height': '450px'
        });

        setThisChatData(layero.find('.'+ ELEM_CHAT_BOX).children('.layim-chat:last'));
        syncGray(data);

        hotkeySend();
        setBackgroundImage(layero);
        setHistory(data);

        renderChatlog();
        showOffMessage();

        // 查看大图
        layero.on('dblclick', '.layim-chat-text img', function(){
          var src = this.src;
          layer.close(popchat.photosIndex);
          layer.photos({
            photos: {
              data: [{
                "alt": "大图模式",
                "src": src
              }]
            },
            anim: 5,
            resize: false,
            success: function(layero, index) {
               popchat.photosIndex = index;
            }
          });
        });

        var thisChat = currentChat();

        layui.event.call(thisChat.elem, MOD_NAME, 'chatChange', thisChat); // 聊天窗口切换事件
        layui.event.call(thisChat.elem, MOD_NAME, 'chatInit', thisChat); // 聊天窗口初始打开事件

        typeof data.success === 'function' && data.success.apply(data, arguments);
      },
      full: function(layero){
        layer.style(index, {
          width: '100%',
          height: '100%'
        }, true);
        resizeChat();
      },
      resizing: resizeChat,
      restore: resizeChat,
      min: function(){
        setChatMin();
        return false;
      },
      end: function(){
        layer.closeAll('tips');
        layimChat = null;
      }
    }, data.layer));

    return index;
  };

  // 同步置灰状态
  var syncGray = function(data){
    $('.layim-'+data.type+data.id).each(function(){
      if($(this).hasClass('layim-list-gray')){
        layui.layim.setFriendStatus(data.id, 'offline');
      }
    });
  };

  // 重置聊天窗口大小
  var resizeChat = function(){
    var list = layimChat.find('.layim-chat-list');
    var chatMain = layimChat.find('.layim-chat-main');
    var chatHeight = layimChat.height();
    var headerHeight = layimChat.find('.layui-layer-title').outerHeight() || 52;
    var footerHeight = layimChat.find('.layim-chat-footer').outerHeight() || 150;
    list.css({
      height: chatHeight
    });
    chatMain.css({
      height: chatHeight - 52 - 150
    });
  };

  // 设置聊天窗口最小化 & 新消息提醒
  var setChatMin = LAYIM.prototype.setChatMin = function(data) {
    var thisChat = data || currentChat().data;
    var options = layui.layim.cache().options;

    if(layimChat && !data){
      layimChat.hide();
    }

    layer.close(setChatMin.index);
    setChatMin.index = layer.open($.extend({
      type: 1,
      title: false,
      skin: 'layui-layim-min layui-layim-layer',
      shade: false,
      closeBtn: false,
      anim: 2,
      offset: 'b',
      move: '#layui-layim-min',
      resize: false,
      content: '<img id="layui-layim-min" src="'+ (thisChat.avatar || cache.options.defaultAvatar) +'"><span>'+ thisChat.username +'</span>',
      success: function(layero, index) {
        if(!data) layimMin = layero;

        // 点击消息提示层
        layero.find('.layui-layer-content span').on('click', function(){
          layer.close(index);
          data ? layui.each(cache.chat, function(i, item){
            popchat(item);
          }) : layimChat.show();
          if (data) {
            cache.chat = [];
            chatListMore();
          }
        });
        layero.find('.layui-layer-content img').on('click', function(e) {
          layui.stope(e);
        });
      }
    }, data && data.layer));
  };

  /**
   * 打开申请好友面板、申请群聊面板、添加好友面板
   * @param {Object} data
   * @param {string} type
   * @returns
   */
  var openRequestContactsPanel = LAYIM.prototype.openRequestContactsPanel = function(data, type) {
    data = data || {};

    // 面板标题
    var title = type === 'addFriend' ? '同意添加好友' : (
      {
        friend: '申请好友',
        group: '申请群聊'
      }[data.type] || ''
    );

    layer.close(openRequestContactsPanel.index);
    return openRequestContactsPanel.index = layer.open({
      type: 1,
      area: '430px',
      title: title,
      shade: false,
      resize: false,
      btn: type ? ['确认', '取消'] : ['发送申请', '关闭'],
      content: laytpl(elemAddTpl).render({
        data: {
          username: data.username || data.groupname,
          avatar: data.avatar || cache.options.defaultAvatar,
          group: data.group || parent.layui.layim.cache().friend || [],
          type: data.type
        },
        type: type
      }),
      yes: function(index, layero) {
        var groupElem = layero.find('#LAY_layimGroup');
        var remarkElem = layero.find('#LAY_layimRemark');
        var obj = {
          gid: groupElem.val(),
          note: remarkElem.val(),
          index: index
        };
        typeof data.onConfirm === 'function' && data.onConfirm(obj);
      },
      btn1: function(index, layero) {
        typeof data.onCancel === 'function' && data.onCancel({
          index: index
        });
      }
    });
  };
  // 打开同意添加好友面板
  LAYIM.prototype.openAddFriendPanel = function(data){
    return openRequestContactsPanel(data, 'addFriend'), this;
  };

  // 切换聊天
  var changeChat = function(elem, del){
    elem = elem || $('.layim-chat-list .' + THIS);
    var index = elem.index() === -1 ? 0 : elem.index();
    var str = '.layim-chat';
    var chatElem = layimChat.find(str).eq(index);
    var hasFull = layimChat.find('.layui-layer-max').hasClass('layui-layer-maxmin');

    if (del) {
      // 如果关闭的是当前聊天，则切换聊天焦点
      if(elem.hasClass(THIS)){
        changeChat(index === 0 ? elem.next() : elem.prev());
      }

      var length = layimChat.find(str).length;

      // 关闭聊天界面
      if(length === 1){
        return layer.close(chatIndex);
      }

      elem.remove();
      chatElem.remove();

      // 只剩下 1 个列表，隐藏左侧区块
      if(length === 2){
        layimChat.find('.layim-chat-list').hide();
        if(!hasFull){
          layimChat.removeClass('layui-layim-chat-more').css('width', '600px');
        }
        layimChat.find('.layim-chat-box').css('margin-left', 0);
        layimChatLayer && layimChatLayer.offset();
      }
      return false;
    }

    elem.addClass(THIS).siblings().removeClass(THIS);
    chatElem.addClass(SHOW).siblings(str).removeClass(SHOW);
    chatElem.find('.layim-loading')[0] || chatElem.find('textarea').focus();

    // 聊天窗口的切换事件
    var thisChat = currentChat();
    layui.event.call(thisChat.elem, MOD_NAME, 'chatChange', thisChat);

    showOffMessage();
  };

  // 展示存在队列中的消息
  var showOffMessage = function(){
    var thisChat = currentChat();
    var message = cache.message[thisChat.data.type + thisChat.data.id];
    if (message) {
      // 展现后，删除队列中消息
      delete cache.message[thisChat.data.type + thisChat.data.id];
    }
  };

  // 获取当前聊天面板
  var currentChat = LAYIM.prototype.currentChat = function(index) {
    if(!layimChat) return;
    index = index === undefined ? $('.layim-chat-list .' + THIS).index() : index;

    var thisChatElem = layimChat.find('.layim-chat').eq(index);
    var textarea = thisChatElem.find('.layim-chat-textarea>textarea');
    var receiver = thisChatElem.data('receiver');

    return {
      elem: thisChatElem,
      data: receiver,
      textarea: textarea
    };
  };

  // 设置初始背景图
  var setBackgroundImage = function(layero) {
    var options = cache.options;
    var local = getLocalData();
    var backgroundImage = local.backgroundImage;
    layero.css({
      'background-image': backgroundImage ? 'url('+ backgroundImage +')' : function(){
        return options.backgroundImage
          ? 'url('+ resPath('images/background/'+ options.backgroundImage) +')'
        : 'none';
      }()
    });
  };

  // 记录历史会话
  var setHistory = function(data) {
    var options = cache.options;
    var local = getLocalData();
    var obj = {};
    var history = local.history || {};
    var is = history[data.type + data.id];

    if(!layimMain) return;

    var historyElem = layimMain.find('.layim-list-history');

    data.historyTime = new Date().getTime();
    history[data.type + data.id] = data;

    local.history = history;

    layui.data(options.LOCAL_TABLE, {
      key: cache.user.id,
      value: local
    });

    if(is) return;

    obj[data.type + data.id] = data;

    var historyList = laytpl(listTpl({
      type: 'history',
      item: 'd.data'
    })).render({data: obj});

    historyElem.prepend(historyList);
    historyElem.find('.layim-null').remove();
  };

  // 发送消息
  var sendMessage = LAYIM.prototype.sendMessage = function(rawContent) {
    var options = cache.options;
    var user = {
      username: cache.user ? cache.user.username : '访客',
      avatar: cache.user ? cache.user.avatar : options.defaultAvatar,
      id: cache.user ? cache.user.id : null,
      user: true
    };
    var thisChat = currentChat();
    var receiver = thisChat.data;
    var isAIModel = receiver.type === 'ai';
    var ul = thisChat.elem.find('.layim-chat-main > ul');
    var textarea = thisChat.textarea;

     // 发送的原始内容
    user.content = rawContent = String(rawContent || textarea.val());

    // 开始发送内容
    if (rawContent.replace(/\s/g, '') !== '') {
      // 渲染内容
      ul.append(laytpl(elemChatMain).render($.extend({}, user, {
        content: contentParser(rawContent)
      })));

      // code 渲染
      codeRender(ul.children('li:last-child'));

      // Pending
      if (isAIModel) chatPending();

      var param = {
        user: user,
        receiver: receiver
      };
      var message = {
        username: user.username,
        avatar: user.avatar || options.defaultAvatar,
        id: receiver.id,
        type: receiver.type,
        content: rawContent,
        timestamp: new Date().getTime(),
        user: true
      };

      pushChatlog(message);

      // 发送消息事件
      layui.event.call(thisChat.elem, MOD_NAME, 'sendMessage', param);
    }

    chatListMore();
    textarea.val('');
    isAIModel ? textarea.blur() : textarea.focus();
  };

  // 消息 Pending
  var chatPending = function(index, resolve) {
    var options = cache.options;
    var thisChat = currentChat(index);
    var receiver = thisChat.data;
    var chatElem = thisChat.elem;
    var ul = thisChat.elem.find('.layim-chat-main > ul');

    var loadElem = '<div class="layim-loading"></div>';
    var existLoadElem = chatElem.find('.layim-loading');
    var findPendingElem = function() {
      return ul.children('li['+ PENDING +']').children('.layim-chat-text');
    };

    clearTimeout(ul.data('timer'));

    // 生成或移除
    if (typeof resolve === 'function') {
      resolve(findPendingElem(), existLoadElem);
    } else {
      chatElem.append(loadElem);
      ul.append(laytpl(elemChatMain).render($.extend({}, receiver, {
        content: '<i class="layui-icon layui-icon-loading-1 layui-anim layui-anim-rotate layui-anim-loop"></i> 正在思考中…',
        status: PENDING
      })));
      // 消息超时
      ul.data('timer', setTimeout(function() {
        findPendingElem().html('<strong>Error:</strong> Message timeout');
        chatElem.find('.layim-loading').remove();
      }, options.chatTimeout));
    }
  };

  // 桌面消息提醒
  var notice = function(data){
    data = data || {};
    if (window.Notification) {
      if(Notification.permission === 'granted'){
        var notification = new Notification(data.title || '', {
          body: data.content || '',
          icon: data.avatar || cache.options.defaultAvatar
        });
      } else {
        Notification.requestPermission();
      };
    }
  };

  // 消息声音提醒
  var voice = function() {
    if(device.ie && device.ie < 9) return;
    var audio = document.createElement("audio");
    audio.src = resPath('voice/'+ cache.options.voice);
    audio.play();
  };

  // 接受消息
  var getMessage = LAYIM.prototype.getMessage = function(data) {
    // 默认选项
    data = $.extend({
      finished: true,
      saveLocalChatlog: true
    }, data);

    var options = cache.options;
    var user = cache.user;
    var local = getLocalData();
    var isAIModel = data.type === 'ai';
    var elem = $('.layim-chatlist-'+ data.type + data.id);
    var group = {};
    var index = elem.index();

    data.timestamp = data.timestamp || new Date().getTime();

    // 是否自身消息
    if (data.type === 'group') {
      if (data.uid === user.id) {
        data.user = true;
      }
    } else {
      if (data.id === user.id) {
        data.user = true;
      }
    }

    // 消息是否为 finished 状态
    if (data.finished) {
      // 添加本地聊天记录
      if (data.saveLocalChatlog) {
        pushChatlog(data);
      }

      // 提示音 - AI 模式强制不开启
      if (options.voice && !isAIModel) {
        $('body').trigger('click')
        getMessage.init ? voice() : (getMessage.init = true);
      }

      // 若收到消息后，对应面板未打开
      if ((!layimChat && data.content) || index === -1) {
        if(cache.message[data.type + data.id]){
          cache.message[data.type + data.id].push(data)
        } else {
          cache.message[data.type + data.id] = [data];

          // 记录聊天面板队列
          if (data.type === 'friend') {
            var friend;
            layui.each(cache.friend, function(index1, item1){
              layui.each(item1.list, function(index, item){
                if(item.id == data.id){
                  item.type = 'friend';
                  item.username = item.username;
                  cache.chat.push(item);
                  return friend = true;
                }
              });
              if(friend) return true;
            });
            if(!friend){
              data.temporary = true; // 临时会话
              cache.chat.push(data);
            }
          } else if(data.type === 'group') {
            var showGroup;
            layui.each(cache.group, function(index, item){
              if(item.id == data.id){
                item.type = 'group';
                item.username = item.groupname;
                cache.chat.push(item);
                return showGroup = true;
              }
            });
            if(!showGroup){
              data.username = data.groupname;
              cache.chat.push(data);
            }
          } else {
            data.username = data.username || data.groupname;
            cache.chat.push(data);
          }
        }
        if(data.type === 'group'){
          layui.each(cache.group, function(index, item){
            if(item.id == data.id){
              group.avatar = item.avatar || cache.options.defaultAvatar;
              return true;
            }
          });
        }
        // 对应会话面板未打开时的消息提示
        if (!data.system) {
          if (options.notice && !isAIModel) {
            notice({
              title: '来自 '+ data.username +' 的消息',
              content: data.content,
              avatar: group.avatar || data.avatar || options.defaultAvatar
            });
          }
          return setChatMin({
            username: '收到新消息',
            avatar: group.avatar || data.avatar || options.defaultAvatar,
            anim: 6
          });
        }
      }
    }

    if (!layimChat) return;

    var thisChat = currentChat();

    if (data.finished) {
      // 接受到的消息不在当前 Tab
      if(thisChat.data.type + thisChat.data.id !== data.type + data.id){
        elem.addClass('layui-anim layer-anim-06');
        setTimeout(function(){
          elem.removeClass('layui-anim layer-anim-06')
        }, 300);
      }
    }

    var chatElem = layimChat.find('.layim-chat').eq(index);
    var ul = chatElem.find('.layim-chat-main > ul');
    var textarea = thisChat.textarea;
    var rawContent = data.content; // 原始内容

    // 系统消息
    if (data.system) {
      if(index !== -1){
        ul.append(
          '<li class="layim-chat-system">'
            +'<span'+ (data.saveChatlog ? ' class="layui-font-green"' : '') +'>'+ rawContent +'</span>'
          +'</li>'
        );
      }
    } else {
      var content = contentParser(rawContent);
      // 若存在 pending 元素，则将内容插入其中；否则生成一个新的内容列表
      chatPending(index, function(elem, loadElem) {
        elem[0] ? elem.html(content) : ul.append(laytpl(elemChatMain).render($.extend({}, data, {
          content: content
        })));
        if (data.finished) {
          elem.closest('li['+ PENDING +']').removeAttr(PENDING);
          loadElem.remove();
        }
      });
    }

    chatListMore();
    textarea.focus();

    // code
    codeRender(ul.children('li:last-child'));
  };

  /**
   * code 渲染
   * @param {HTMLElement} elem
   */
  var codeRender = function(elem) {
    var options = cache.options;
    var local = getLocalData();
    layui.code({
      elem: elem.find('pre>code'),
      theme: options.theme || local.theme || ''
    });
  };

  // 消息盒子的提醒
  var ANIM_MSG = 'layui-anim-loop layer-anim-05';
  LAYIM.prototype.setMsgboxCount = function(num){
    var msgboxElem = layimMain.find('.layim-tool-msgbox');
    msgboxElem.find('span').addClass(ANIM_MSG).html(num);
  };

  // 存储最近 LOCAL_MAX_CHATLOG 条聊天记录到本地
  var pushChatlog = function(message) {
    var options = cache.options;
    var local = getLocalData();
    var thisChat = currentChat() || {};
    var data = thisChat.data || {};

    // 当前会话是否启用本地聊天记录，默认启用
    var enableLocalChatlog = 'enableLocalChatlog' in data ? data.enableLocalChatlog : true;
    if (!enableLocalChatlog) return;

    // 获取本地聊天记录
    local.chatlog = local.chatlog || {};
    var thisChatlog = local.chatlog[message.type + message.id];

    if (thisChatlog) {
      // 避免浏览器多窗口时聊天记录重复保存
      var nosame;
      layui.each(thisChatlog, function(index, item){
        if((item.timestamp === message.timestamp
          && item.type === message.type
          && item.id === message.id
        && item.content === message.content)){
          nosame = true;
        }
      });
      if(!(nosame || message.fromid == cache.user.id)){
        thisChatlog.push(message);
      }
      if(thisChatlog.length > options.LOCAL_MAX_CHATLOG){
        thisChatlog.shift();
      }
    } else {
      local.chatlog[message.type + message.id] = [message];
    }
    layui.data(options.LOCAL_TABLE, {
      key: cache.user.id,
      value: local
    });
  };

  // 渲染聊天记录到相应面板
  var renderChatlog = function(done) {
    var options = cache.options;
    var local = getLocalData();
    var thisChat = currentChat();
    var data = thisChat.data;
    var localChatlog = (local.chatlog || {})[data.type + data.id]; // 本地聊天记录
    var chatlogHandle = callback('chatlog'); // 获取聊天记录回调函数

    // 渲染聊天记录列表
    var render = function(chatlogData) {
      var ul = thisChat.elem.find('.layim-chat-main ul');
      var lists = [];
      if (layui.type(chatlogData) !== 'array') return; // 若数据非数组格式，则中止渲染
      layui.each(chatlogData, function(index, item) {
        if (item.system) {
          lists.push('<li class="layim-chat-system"><span class="layui-font-green">'+ item.content +'</span></li>')
        } else {
          lists.push(laytpl(elemChatMain).render($.extend({}, item, {
            content: contentParser(item.content)
          })));
        }
      });
      ul.append(lists.join(''));

      chatListMore(); // 顶部显示更多记录文本
      codeRender(thisChat.elem); // code
      typeof done === 'function' && done(); // calkback
    };

    // 若存在回调
    if (typeof chatlogHandle === 'function') {
      var rst = chatlogHandle({
        user: cache.user,
        receiver: data
      });
      // 是否返回 Promise
      return (rst && typeof rst.then === 'function')
        ? rst.then(render, console.error)
        : render(rst);
    }

    render(localChatlog);
  };

  /**
   * 添加联系人到主面板列表
   * @param {Object} data
   * @param {('friend' | 'group')} data.type - 联系人类型
   * @param {string} data.id - 联系人 ID
   * @param {string} data.username - 联系人名称
   * @param {string} data.avatar - 联系人头像
   * @param {string} data.gid - 联系人分组 ID，若 type 为 friend 时
   * @param {string} data.sign - 联系人签名，若 type 为 friend 时
   */
  LAYIM.prototype.addContacts = function(data) {
    var obj = {};
    var has;
    var listElem = layimMain.find('.layim-list-'+ data.type);

    if (cache[data.type]) {
      if(data.type === 'friend'){
        layui.each(cache.friend, function(index, item) {
          if(data.gid == item.id){
            // 好友是否已经在列表中
            layui.each(cache.friend[index].list, function(idx, itm) {
              if(itm.id == data.id){
                return has = true
              }
            });
            if (has) return layer.msg('好友 ['+ (data.username||'') +'] 已经存在列表中',{anim: 6});
            cache.friend[index].list = cache.friend[index].list || [];
            obj[cache.friend[index].list.length] = data;
            data.groupIndex = index;
            cache.friend[index].list.push(data); //在cache的friend里面也增加好友
            return true;
          }
        });
      } else if(data.type === 'group') {
        // 检查群组是否已经在列表中
        layui.each(cache.group, function(idx, itm) {
          if(itm.id == data.id){
            return has = true
          }
        });
        if(has) return layer.msg('您已是 ['+ (data.groupname||'') +'] 的群成员',{anim: 6});
        obj[cache.group.length] = data;
        cache.group.push(data);
      }
    }

    if (has) return;

    var list = laytpl(listTpl({
      type: data.type,
      item: 'd.data',
      index: data.type === 'friend' ? 'data.groupIndex' : null
    })).render({data: obj});

    if(data.type === 'friend'){
      var li = listElem.find('>li').eq(data.groupIndex);
      li.find('.layui-layim-list').append(list);
      li.find('.layim-count').html(cache.friend[data.groupIndex].list.length); // 刷新好友数量
      // 如果初始没有好友
      if(li.find('.layim-null')[0]){
        li.find('.layim-null').remove();
      }
    } else if(data.type === 'group'){
      listElem.append(list);
      // 如果初始没有群组
      if(listElem.find('.layim-null')[0]){
        listElem.find('.layim-null').remove();
      }
    }
  };

  /**
   * 从主面板列表移除联系人
   * @param {Object} data
   * @param {('friend' | 'group')} data.type - 联系人类型
   * @param {string} data.id - 联系人 ID
   */
  LAYIM.prototype.removeContacts = function(data) {
    var listElem = layimMain.find('.layim-list-'+ data.type);
    var obj = {};
    if (cache[data.type]) {
      if (data.type === 'friend') {
        layui.each(cache.friend, function(index1, item1) {
          layui.each(item1.list, function(index, item) {
            if(data.id == item.id){
              var li = listElem.find('>li').eq(index1);
              var list = li.find('.layui-layim-list>li');
              li.find('.layui-layim-list>li').eq(index).remove();
              cache.friend[index1].list.splice(index, 1); // 从 cache 的 friend 里面也删除掉好友
              li.find('.layim-count').html(cache.friend[index1].list.length); // 刷新好友数量
              // 如果一个好友都没了
              if(cache.friend[index1].list.length === 0){
                li.find('.layui-layim-list').html('<li class="layim-null">该分组下已无好友了</li>');
              }
              return true;
            }
          });
        });
      } else if(data.type === 'group') {
        layui.each(cache.group, function(index, item) {
          if (data.id == item.id) {
            listElem.find('>li').eq(index).remove();
            cache.group.splice(index, 1); // 从 cache 的 group 里面也删除掉数据
            // 若无群组
            if (cache.group.length === 0) {
              listElem.html('<li class="layim-null">暂无群组</li>');
            }
            return true;
          }
        });
      }
    }
  };

  // 查看更多记录
  var chatListMore = function() {
    var options = cache.options;
    var thisChat = currentChat();
    var chatMain = thisChat.elem.find('.layim-chat-main');
    var ul = chatMain.children('ul');
    var li = ul.children('li');
    var length = li.length;
    var LOCAL_MAX_CHATLOG = options.LOCAL_MAX_CHATLOG;

    // 是否超出本地聊天记录的最大数量限制
    if (length >= LOCAL_MAX_CHATLOG) {
      var first = li.eq(0);
      if(!ul.prev().hasClass('layim-chat-system')){
        ul.before('<div class="layim-chat-system"><span layim-event="chatlog">…</span></div>');
      }
      if (length > LOCAL_MAX_CHATLOG) {
        first.remove();
      }
    }

    chatMain.scrollTop(chatMain[0].scrollHeight + 1000);
    li.last().find('img').on('load', function(){
      chatMain.scrollTop(chatMain[0].scrollHeight + 1000);
    });
  };

  // 快捷键发送
  var hotkeySend = function(){
    var thisChat = currentChat();
    var textarea = thisChat.textarea;
    textarea.focus();
    // event
    textarea.off('keydown').on('keydown', function(e) {
      var local = getLocalData();
      var keyCode = e.keyCode;
      if(local.sendHotKey === 'Ctrl+Enter'){
        if(e.ctrlKey && keyCode === 13){
          sendMessage();
        }
        return;
      }
      if(keyCode === 13){
        if(e.ctrlKey){
          return textarea.val(textarea.val()+'\n');
        }
        if(e.shiftKey) return;
        e.preventDefault();
        sendMessage();
      }
    });
  };

  // 表情库
  var faces = function(){
    var obj = {
      "吼吼": "^O^",
      "惊讶": "w(ﾟДﾟ)w",
      "不屑": " (￣_,￣ )",
      "好耶": "ヽ(✿ﾟ▽ﾟ)ノ",
      "亲": "o(*￣3￣)o",
      "棒": "(๑•̀ㅂ•́)و✧",
      "涨": "  (￣︶￣)↗",
      "得意": "<(￣︶￣)>",
      "挖鼻孔": " (*￣rǒ￣)",
      "惊": "⊙ˍ⊙",
      "飞": "︿(￣︶￣)︿",
      "哼哼": "o(￣ヘ￣o＃)",
      "好滴": " (u‿ฺu✿ฺ)",
      "啊啊": "Ｏ(≧口≦)Ｏ",
      "啦啦": "♪(^∇^*)",
      "惊喜": "╰(*°▽°*)╯",
      "乖": " o(*^＠^*)o",
      "陶醉": "( *︾▽︾)",
      "喂": " (#`O′)",
      "愣住": " (°ー°〃)",
      "放屁": " ○|￣|_ =3",
      "可恶": "（＝。＝）",
      "生气": " (ー`′ー)",
      "满足": " o(*￣︶￣*)o",
      "崩溃": "o(≧口≦)o",
      "呃呃呃": "(⊙﹏⊙)",
      "晕": "X﹏X",
      "呆": " ━┳━　━┳━",
      "喵星人": " ( =•ω•= )m",
      "喵呜": " ≡ω≡",
      "熊": "(*￣(エ)￣)",
      "害羞": " (✿◡‿◡)",
      "good": " o(￣▽￣)ｄ",
      "可爱": "*＾-＾*",
      "爆笑": "ヾ(≧▽≦*)o",
      "卖萌": "=￣ω￣="
    };
    return obj;
  }();

  // 在焦点处插入内容
  var focusInsert = function(obj, str){
    var result;
    var val = obj.value;
    obj.focus();
    if (document.selection) { // ie
      result = document.selection.createRange();
      document.selection.empty();
      result.text = str;
    } else {
      result = [val.substring(0, obj.selectionStart), str, val.substr(obj.selectionEnd)];
      obj.focus();
      obj.value = result.join('');
    }
  };

  // 事件
  var anim = 'layui-anim-upbit';
  var events = {
    // 在线状态
    status: function(othis, e) {
      var hide = function(){
        othis.next().hide().removeClass(anim);
      };
      var type = othis.attr('lay-type');
      if(type === 'show'){
        layui.stope(e);
        othis.next().show().addClass(anim);
        $(document).off('click', hide).on('click', hide);
      } else {
        var prev = othis.parent().prev();
        othis.addClass(THIS).siblings().removeClass(THIS);
        prev.html(othis.find('cite').html());
        prev.removeClass('layim-status-'+(type === 'online' ? 'hide' : 'online'))
        .addClass('layim-status-'+type);

        // 设置在线事件
        layui.event.call(othis[0], MOD_NAME, 'online', {
          status: type
        });
      }
    },

    // 编辑签名
    sign: function() {
      var input = layimMain.find('.layui-layim-remark');
      input.on('change', function(){
        var value = this.value;
        // 编辑签名事件
        layui.event.call(this, MOD_NAME, 'sign', {
          value: value
        });
      });
      input.on('keyup', function(e){
        var keyCode = e.keyCode;
        if(keyCode === 13){
          this.blur();
        }
      });
    },

    // 大分组切换
    tab: function(othis) {
      var index, main = '.layim-tab-content';
      var tabs = layimMain.find('.layui-layim-tab>li');
      typeof othis === 'number' ? (
        index = othis
        ,othis = tabs.eq(index)
      ) : (
        index = othis.index()
      );
      index > 2 ? tabs.removeClass(THIS) : (
        events.tab.index = index
        ,othis.addClass(THIS).siblings().removeClass(THIS)
      )
      layimMain.find(main).eq(index).addClass(SHOW).siblings(main).removeClass(SHOW);
    },

    // 展开联系人分组
    spread: function(othis) {
      var type = othis.attr('lay-type');
      var spread = type === 'true' ? 'false' : 'true';
      var options = cache.options;
      var local = getLocalData();

      othis.next()[type === 'true' ? 'removeClass' : 'addClass'](SHOW);
      local['spread' + othis.parent().index()] = spread;
      layui.data(options.LOCAL_TABLE, {
        key: cache.user.id,
        value: local
      });
      othis.attr('lay-type', spread);
      othis.find('.layui-icon').html(spread === 'true' ? '&#xe61a;' : '&#xe602;');
    },

    // 搜索
    search: function(othis) {
      var search = layimMain.find('.layui-layim-search');
      var main = layimMain.find('#layui-layim-search');
      var input = search.find('input'), find = function(e){
        var val = input.val().replace(/\s/);
        if (val === '') {
          events.tab(events.tab.index|0);
        } else {
          var data = [], friend = cache.friend || [];
          var group = cache.group || [], html = '';
          for(var i = 0; i < friend.length; i++){
            for(var k = 0; k < (friend[i].list||[]).length; k++){
              if(friend[i].list[k].username.indexOf(val) !== -1){
                friend[i].list[k].type = 'friend';
                friend[i].list[k].index = i;
                friend[i].list[k].list = k;
                data.push(friend[i].list[k]);
              }
            }
          }
          for(var j = 0; j < group.length; j++){
            if(group[j].groupname.indexOf(val) !== -1){
              group[j].type = 'group';
              group[j].index = j;
              group[j].list = j;
              data.push(group[j]);
            }
          }
          if(data.length > 0){
            for(var l = 0; l < data.length; l++){
              html += '<li layim-event="chat" data-type="'+ data[l].type +'" data-index="'+ data[l].index +'" data-list="'+ data[l].list +'"><img src="'+ (data[l].avatar || cache.options.defaultAvatar) +'"><span>'+ (data[l].username || data[l].groupname || '佚名') +'</span><p>'+ (data[l].remark||data[l].sign||'') +'</p></li>';
            }
          } else {
            html = '<li class="layim-null">无搜索结果</li>';
          }
          main.html(html);
          events.tab(3);
        }
      };

      var contactsPanel = cache.options.contactsPanel;
      if(!contactsPanel.showFriend && contactsPanel.showGroup){
        events.tab.index = 1;
      } else if(!contactsPanel.showFriend && !contactsPanel.showGroup){
        events.tab.index = 2;
      }

      search.show();
      input.focus();
      input.off('keyup', find).on('keyup', find);
    },

    // 关闭搜索
    closeSearch: function(othis) {
      othis.parent().hide();
      events.tab(events.tab.index|0);
    },

    // 消息盒子
    msgbox: function() {
      var msgboxElem = layimMain.find('.layim-tool-msgbox');
      var options = cache.options;
      var pageurl = options.pageurl || {};
      var url = typeof pageurl.msgbox === 'function' ? pageurl.msgbox({
        config: options,
        user: cache.user
      }) : pageurl.msgbox;

      if (typeof url !== 'string') return;

      layer.close(events.msgbox.index);
      msgboxElem.find('span').removeClass(ANIM_MSG).html('');

      return events.msgbox.index = layer.open({
        type: 2,
        title: '消息盒子',
        shade: false,
        maxmin: true,
        area: ['600px', '518px'],
        skin: 'layui-layer-border',
        resize: false,
        content: url
      });
    },

    // 弹出查找页面
    find: function() {
      var options = cache.options;
      var pageurl = options.pageurl || {};
      var url = typeof pageurl.find === 'function' ? pageurl.find({
        config: options,
        user: cache.user
      }) : pageurl.find;

      if (typeof url !== 'string') return;

      layer.close(events.find.index);
      return events.find.index = layer.open({
        type: 2,
        title: '查找',
        shade: false,
        maxmin: true,
        area: ['1000px', '518px'],
        skin: 'layui-layer-border',
        resize: false,
        content: url
      });
    },

    // 设置
    set: function() {
      var options = cache.options;
      // 内置背景图可选列表
      var backgroundImages = (function() {
        var arr = [];
        layui.each(Array(5), function(index) {
          arr.push(resPath('images/background/'+ (index + 1) +'.jpg'));
        });
        return arr;
      })();
      layer.open({
        type: 1,
        title: '设置',
        shade: false,
        area: '260px',
        anim: 5,
        skin: 'layui-layer-border layui-layim-layer',
        id: 'layui-layim-set',
        zIndex: layer.zIndex + 999999,
        resize: false,
        content: laytpl(elemSetTpl).render({
          backgroundImages: backgroundImages
        }),
        success: function(layero, index, that) {
          form.render(layero.find('.layui-form'));

          // 初始化主题模式
          var local = getLocalData();
          var setThemeMode = function(checked){
            // 选中状态
            if (checked) {
              if(device.ie) return;
              local.theme = 'dark';
              // 标注选中
              form.val('layim-set', {
                'theme': true
              });
            } else {
              delete local.theme;
            }
            layui.data(options.LOCAL_TABLE, {
              key: cache.user.id,
              value: local
            });
          };

          setThemeMode(local.theme === 'dark');

          // 主题开关事件
          form.on('switch(layim-theme-mode)', function(obj){
            var checked = obj.elem.checked;
            setThemeMode(obj.elem.checked);
            theme(checked ? 'dark' : 'light');
          });
        }
      });
    },

    // 设置背景图
    setBackgroundImage: function(othis) {
      var src = othis.attr('src');
      var options = cache.options;
      var local = getLocalData();
      local.backgroundImage = src;
      if(!src) delete local.backgroundImage;
      layui.data(options.LOCAL_TABLE, {
        key: cache.user.id,
        value: local
      });
      try {
        layimMain.css({
          'background-image': src ? 'url('+ src +')' : 'none'
        });
        layimChat.css({
          'background-image': src ? 'url('+ src +')' : 'none'
        });
      } catch(e) { }
      // 更换背景图的事件
      var filename = (src || '').replace(resPath('images/background'), '');
      layui.event.call(othis[0], MOD_NAME, 'backgroundImage', {
        filename: filename,
        src: src
      });
    },

    // 弹出聊天面板
    chat: function(othis) {
      var local = getLocalData();
      var type = othis.data('type');
      var index = othis.data('index');
      var list = othis.attr('data-list') || othis.index();
      var data = {};
      if(type === 'friend'){
        data = cache[type][index].list[list];
      } else if(type === 'group'){
        data = cache[type][list];
      } else if(type === 'history'){
        data = (local.history || {})[index] || {};
      }
      data.username = data.username || data.groupname;
      if(type !== 'history'){
        data.type = type;
      }
      popchat(data);
    },

    // 切换聊天
    tabChat: function(othis) {
      changeChat(othis);
    },

    // 关闭聊天列表
    closeChat: function(othis, e) {
      changeChat(othis.parent(), 1);
      layui.stope(e);
    },
    closeThisChat: function() {
      changeChat(null, 1);
    },

    // 展开群组成员
    groupMembers: function(othis, e) {
      var icon = othis.find('.layui-icon');
      // 关闭群员面板
      var closeMembersPanel = function() {
        icon.html('&#xe61a;');
        othis.data('down', null);
        layer.close(events.groupMembers.index);
      };
      var NAMESPACE = '.layim.open.members';

      // 展开或收缩
      if (othis.data('down')) {
        closeMembersPanel();
      } else {
        var UL = '<ul class="layim-members-list layim-border"></ul>';
        icon.html('&#xe619;');
        othis.data('down', true);
        events.groupMembers.index = layer.tips(UL, othis, {
          tips: 3,
          time: 0,
          anim: 5,
          isOutAnim: false,
          fixed: true,
          skin: 'layui-layim-members layui-layim-layer',
          success: function(layero) {
            var thisChat = currentChat()
            var ul = layero.find('.layim-members-list');
            var li = [];
            var membersCache = {};
            var hasFull = layimChat.find('.layui-layer-max').hasClass('layui-layer-maxmin');
            var listNone = layimChat.find('.layim-chat-list').css('display') === 'none';

            if (hasFull) {
              ul.css({
                width: $(window).width() - 22 - (listNone || 200)
              });
            }

            loading(ul);

            // 查看群员事件
            layui.event.call(othis[0], MOD_NAME, 'viewMmembers', {
              ajax: ajax,
              layero: layero,
              receiver: thisChat.data,
              render: function(res) {
                layui.each(res.list, function(index, item) {
                  li.push([
                    '<li data-uid="'+ item.id +'">',
                      '<a href="javascript:;">',
                        '<img src="'+ (item.avatar || cache.options.defaultAvatar) +'">',
                        '<cite>'+ item.username +'</cite>',
                      '</a>',
                    '</li>'
                  ].join(''));
                  membersCache[item.id] = item;
                });

                ul.html(li.join(''));

                // 获取群员
                othis.find('.layim-chat-members').html(res.members||(res.list||[]).length + '人');

                // 私聊
                ul.find('li').on('click', function(){
                  var uid = $(this).data('uid'), info = membersCache[uid]
                  popchat({
                    username: info.username,
                    type: 'friend',
                    avatar: info.avatar || cache.options.defaultAvatar,
                    id: info.id
                  });
                  closeMembersPanel();
                });
              }
            });

            layero.on('mousedown'+ NAMESPACE, function(e){
              layui.stope(e);
            });
          }
        });

        // 点击其他元素关闭群员面板
        $(document).off('mousedown'+ NAMESPACE).on('mousedown'+ NAMESPACE, closeMembersPanel);
        $(window).off('resize'+ NAMESPACE).on('resize'+ NAMESPACE, closeMembersPanel);
        othis.off('mousedown'+ NAMESPACE).on('mousedown'+ NAMESPACE, layui.stope);
      }
    },

    // 发送聊天内容
    send: function() {
      sendMessage();
    },

    // 设置发送聊天快捷键
    setSend: function(othis, e) {
      var options = cache.options;
      var box = events.setSend.box = othis.siblings('.layim-menu-box');
      var type = othis.attr('lay-type');

      if (type === 'show') {
        layui.stope(e);
        box.show().addClass(anim);
        $(document).off('click', events.setSendHide).on('click', events.setSendHide);
      } else {
        var local = getLocalData();
        othis.addClass(THIS).siblings().removeClass(THIS);
        local.sendHotKey = type;
        layui.data(options.LOCAL_TABLE, {
          key: cache.user.id,
          value: local
        });
        events.setSendHide(e, othis.parent());
      }
    },
    setSendHide: function(e, box){
      (box || events.setSend.box).hide().removeClass(anim);
    },

    // 表情
    face: function(othis, e){
      var str = '';
      var thisChat = currentChat();

      for(var key in faces){
        str += '<li title="'+ key +'">'+ faces[key] +'</li>';
      }
      str = '<ul class="layui-clear layim-face-list">'+ str +'</ul>';

      events.face.index = layer.tips(str, othis, {
        tips: 1,
        time: 0,
        fixed: true,
        anim: 5,
        skin: 'layui-layim-face',
        success: function(layero){
          layero.find('.layim-face-list>li').on('mousedown', function(e){
            layui.stope(e);
          }).on('click', function(){
            focusInsert(thisChat.textarea[0], this.innerHTML + ' ');
            layer.close(events.face.index);
          });
        }
      });

      var eventName = '.layim.chatTools.face';
      $(document).off('mousedown'+ eventName, events.faceHide).on('mousedown'+ eventName, events.faceHide);
      $(window).off('resize'+ eventName, events.faceHide).on('resize'+ eventName, events.faceHide);
      layui.stope(e);
    },
    faceHide: function(){
      layer.close(events.face.index);
    },

    // 图片或一般文件
    file: function(othis) {
      var toolname = othis.data('chat-toolname');
      var api = {
        image: 'uploadImage',
        file: 'uploadFile'
      };
      var thisChat = currentChat();
      var conf = cache.options[api[toolname]] || {};

      layui.upload.render({
        url: conf.url || '',
        method: conf.toolname,
        elem: othis.find('input')[0],
        accept: toolname,
        done: function(res) {
          if(res.code == 0){
            var data = res.data = res.data || {};
            var filename = data.filename || '';
            var src = data.src;
            if(toolname === 'image'){
              focusInsert(thisChat.textarea[0], '![]('+ src +')');
            } else if(toolname === 'file'){
              focusInsert(thisChat.textarea[0], '!file['+ (filename || '下载文件') +']('+ src +')');
            }
            sendMessage();
          } else {
            layer.msg(res.msg || '上传失败');
          }
        }
      });
    },

    // 插入远程音频和视频 URL
    remoteMedia: function(othis) {
      var toolname = othis.data('chat-toolname');
      var text = {
        audio: '音频',
        video: '视频'
      };
      var thisChat = currentChat();

      layer.prompt({
        title: '请输入远程'+ text[toolname] + ' URL',
        shade: false,
        anim: 5,
        id: 'LAYIM-LAYER-CHAT-TOOL-'+ toolname,
        offset: [
          othis.offset().top - $(window).scrollTop() - 168 + 'px',
          othis.offset().left + 'px'
        ]
      }, function(src, index){
        focusInsert(thisChat.textarea[0], '!'+ toolname + '('+ src +')');
        sendMessage();
        layer.close(index);
      });
    },

    // 点击扩展聊天工具栏
    extendChatTool: function(othis) {
      var toolname = othis.data('chat-toolname');
      var thisChat = currentChat();
      var chatTools = cache.options.chatTools;
      var currChatTool = (function() {
        var findItem;
        layui.each(chatTools, function(i, item) {
          if (item.name === toolname){
            findItem = item;
            return true;
          }
        });
        return findItem;
      })();

      // 事件
      if (currChatTool) {
        // click 事件
        typeof currChatTool.onClick === 'function' && currChatTool.onClick({
          data: thisChat,
          elem: othis,
          insert: function(content) {
            focusInsert(thisChat.textarea[0], content);
          }
        });
      }
    },

    // 播放音频
    playAudio: function(othis) {
      var audioData = othis.data('audio');
      var audio = audioData || document.createElement('audio');
      var pause = function() {
        audio.pause();
        othis.removeAttr('status');
        othis.find('i').addClass('layui-icon-play').removeClass('layui-icon-pause');
      };
      if(othis.data('error')){
        return layer.msg('播放音频源异常');
      }
      if(!audio.play){
        return layer.msg('您的浏览器不支持 audio');
      }
      if(othis.attr('status')){
        pause();
      } else {
        audioData || (audio.src = othis.data('src'));
        audio.play();
        othis.attr('status', 'pause');
        othis.data('audio', audio);
        othis.find('i').addClass('layui-icon-pause').removeClass('layui-icon-play');
        //播放结束
        audio.onended = function(){
          pause();
        };
        //播放异常
        audio.onerror = function(){
          layer.msg('播放音频源异常');
          othis.data('error', true);
          pause();
        };
      }
    },

    // 播放视频
    playVideo: function(othis) {
      var videoData = othis.data('src');
      var video = document.createElement('video');
      if(!video.play){
        return layer.msg('您的浏览器不支持video');
      }
      layer.close(events.playVideo.index);
      events.playVideo.index = layer.open({
        type: 1,
        title: '播放视频',
        area: ['460px', '300px'],
        maxmin: true,
        shade: false,
        skin: 'layui-layim-layer',
        content: '<div style="background-color: #000; height: 100%;"><video style="position: absolute; width: 100%; height: 100%;" src="'+ videoData +'" loop="loop" autoplay="autoplay"></video></div>'
      });
    },

    // 点击聊天记录
    chatlog: function(othis) {
      var options = cache.options;
      var pageurl = options.pageurl || {};
      var thisChat = currentChat();

      if (!pageurl.chatlog) {
        return layer.msg('未开启更多聊天记录');
      }

      var url = typeof pageurl.chatlog === 'function' ? pageurl.chatlog({
        config: options,
        user: cache.user,
        receiver: thisChat.data
      }) : pageurl.chatlog;
      if (typeof url !== 'string') return;

      layer.close(events.chatlog.index);
      return events.chatlog.index = layer.open({
        type: 2,
        maxmin: true,
        title: '与 <strong>'+ thisChat.data.username +'</strong> 的聊天记录',
        area: ['450px', '100%'],
        shade: false,
        offset: 'r',
        anim: 'slideLeft',
        id: 'layui-layim-chatlog',
        content: url
      });
    },

    // 历史会话右键菜单操作
    menuHistory: function(othis, e) {
      var options = cache.options;
      var local = getLocalData();
      var parent = othis.parent(), type = othis.data('type');
      var hisElem = layimMain.find('.layim-list-history');
      var none = '<li class="layim-null">暂无历史会话</li>'

      if (type === 'one') {
        var history = local.history;
        delete history[parent.data('index')];

        local.history = history;

        layui.data(options.LOCAL_TABLE, {
          key: cache.user.id,
          value: local
        });

        // 删除 DOM
        $('.layim-list-history li.layim-'+parent.data('index')).remove();

        if(hisElem.find('li').length === 0){
          hisElem.html(none);
        }

      } else if(type === 'all') {
        delete local.history;
        layui.data(options.LOCAL_TABLE, {
          key: cache.user.id,
          value: local
        });
        hisElem.html(none);
      }

      layer.closeAll('tips');
    }
  };

  // 批量事件
  util.on('layim-event', events);

  // export api
  exports(MOD_NAME, new LAYIM());
}).link(layui.cache.layimResPath + 'layim.css', 'skinlayimcss') // 加载 css;
